package uk.co.markfrimston.utils;

import java.util.*;

public class QuotedStringTokenizer implements Enumeration<String>
{
	protected static final int MODE_NORMAL = 0;
	protected static final int MODE_TOKEN = 1;
	protected static final int MODE_QUOTED = 2;
	protected static final int MODE_ESCAPE = 3;
	protected static final int MODE_QUOTED_ESCAPE = 4;
	
	protected String string;
	protected Set<Character> delims = new HashSet<Character>();
	protected Map<Character,Character> quotes = new HashMap<Character,Character>();
	protected Set<Character> escapes = new HashSet<Character>();
	protected int pos;
	protected String nextToken;
	protected Character currentChar;
	protected Character currentQuote;
	protected Character currentEscape;
	
	public QuotedStringTokenizer(String string)
	{
		this.string =string;
		this.delims.add(' ');
		this.delims.add('\t');
		this.delims.add('\n');
		this.delims.add('\r');
		this.delims.add('\f');
		this.quotes.put('"','"');
		this.escapes.add('\\');
		this.pos = 0;
		advanceChar();
		advance();
	}
	
	public QuotedStringTokenizer(String string, String delims, String quotePairs, String escapes)
	{
		this.string = string;
		for(char c : delims.toCharArray()){
			this.delims.add(c);
		}
		for(int i=0; i<quotePairs.length(); i+=2){
			Character start = quotePairs.charAt(i);
			Character end = i+1<quotePairs.length() ? quotePairs.charAt(i+1) : quotePairs.charAt(i);
			this.quotes.put(start, end);
		}
		for(char c : escapes.toCharArray()){
			this.escapes.add(c);
		}
		this.pos = 0;
		advanceChar();
		advance();
	}
	
	protected void advanceChar()
	{		
		if(pos<string.length())
		{
			currentChar = string.charAt(pos);
			pos++;
		}
		else
		{
			currentChar = null;
		}		
	}
	
	protected void advance()
	{
		int mode=MODE_NORMAL;
		String buffer = "";
		while(true)
		{			
			switch(mode)
			{
				case MODE_NORMAL:
					// end of input
					if(currentChar==null)
					{
						nextToken = null;
						return;
					}
					// delimiter space
					else if(delims.contains(currentChar))
					{
						advanceChar();
					}
					// start quote
					else if(quotes.containsKey(currentChar))
					{
						mode = MODE_QUOTED;				
						currentQuote = currentChar;
						advanceChar();
					}
					// start escape
					else if(escapes.contains(currentChar))
					{
						mode = MODE_ESCAPE;
						currentEscape = currentChar;
						advanceChar();
					}
					// character data
					else
					{
						mode = MODE_TOKEN;
						buffer += currentChar;
						advanceChar();
					}
					break;
				case MODE_TOKEN:
					// end of input, end of token, start of quoted
					if(currentChar==null
							|| delims.contains(currentChar)
							|| quotes.containsKey(currentChar))
					{
						nextToken = buffer;
						return;
					}
					// start of escape
					else if(escapes.contains(currentChar))
					{
						mode = MODE_ESCAPE;
						advanceChar();
					}
					// character data
					else
					{
						buffer += currentChar;
						advanceChar();
					}
					break;
				case MODE_ESCAPE:
					// end of input
					if(currentChar==null)
					{
						nextToken = buffer;
						return;
					}
					// character data
					else
					{
						mode = MODE_TOKEN;
						buffer += currentChar;
						advanceChar();
					}
					break;
				case MODE_QUOTED:					
					// end of input, end of quote
					if(currentChar==null
							|| currentChar.equals(quotes.get(currentQuote)))
					{
						nextToken = buffer;
						advanceChar();
						return;
					}
					// start escape
					else if(escapes.contains(currentChar))
					{
						mode = MODE_QUOTED_ESCAPE;
						currentEscape = currentChar;
						advanceChar();
					}
					// character data
					else
					{
						buffer += currentChar;
						advanceChar();
					}
					break;
				case MODE_QUOTED_ESCAPE:
					// end of input
					if(currentChar==null)
					{
						nextToken = buffer;
						return;
					}
					// character data
					else
					{
						mode = MODE_QUOTED;
						buffer += currentChar;
						advanceChar();
					}
					break;			
			}
		}
	}
	
	public boolean hasMoreElements()
	{	
		return nextToken!=null;
	}

	public String nextElement()
	{
		String el = nextToken;
		advance();
		return el;
	}
	
	public static void main(String[] args)
	{
		/*String input = "   one two  \\\" 	three   \"four  \\\"!\\\"  \" {f i \" v \\{e\\}} six  ";
		QuotedStringTokenizer tok = new QuotedStringTokenizer(input," \t","\"\"{}","\\");
		while(tok.hasMoreElements())
		{
			System.out.println("["+tok.nextElement()+"]");
		}*/
		
		String input = "\"one\" \"two\" \"three\"";
		QuotedStringTokenizer tok = new QuotedStringTokenizer(input);
		while(tok.hasMoreElements()){
			System.out.println("["+tok.nextElement()+"]");
		}
	}
}
